package com.netx.data;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.InputStream;
import java.sql.SQLException;
import com.netx.generics.basic.ErrorListException;
import com.netx.generics.collections.ImmutableList;
import com.netx.generics.collections.ImmutableMap;
import com.netx.generics.sql.ConnectionPool;
import com.netx.generics.translation.Results;
import com.netx.generics.translation.Translator;
import com.netx.generics.io.BufferedReader;


public class Database {

	// load predefined data sinks:
	static {
		DataSinks.registerClass("file", FileSink.class);
	}

	public static Results init(InputStream in) {
		final DatabaseScanner scanner = new DatabaseScanner(new BufferedReader(in));
		final DatabaseParser parser = new DatabaseParser(scanner);
		final DatabaseAnalyzer analyzer = new DatabaseAnalyzer(parser);
		final DatabaseChecker checker = new DatabaseChecker(analyzer);
		// fool the compiler:
		checker.toString();
		return new Translator(scanner).performWork();
	}

	private final Repository _repository;
	private ConnectionPool _pool;
	private final ImmutableMap<String,MetaData> _metadata;
	private final ImmutableMap<String,EntityMetaData> _entities;
	private final ImmutableList<DataSink> _backup;
	//private final UnmodifiableMap _replication;
	private final Map<String,Connection> _connectionCache;
	private final List<ConnectionListener> _listeners;
	private boolean _closed;

	// for DatabaseAnalyzer:
	Database(Repository repository, Map<String,MetaData> metadata, Map<String,EntityMetaData> entities, List<DataSink> backup, Map<String,DataSink> replication) {
		_repository = repository;
		//TODO this is only used to hold a copy of ALL the metadata (entities + relations),
		// for getMetaData(). Isn't there a cleaner way to achieve this?
		_metadata = new ImmutableMap<String,MetaData>(metadata);
		_entities = new ImmutableMap<String,EntityMetaData>(entities);
		_backup = new ImmutableList<DataSink>(backup);
		//_replication = new UnmodifiableMap(replication);
		_connectionCache = new HashMap<String,Connection>();
		_listeners = new ArrayList<ConnectionListener>();
		_createConnectionPool();
		_closed = false;
	}

	public DatabaseDriver getDriver() {
		return _repository.driver;
	}

	public Connection getConnection() throws DatabaseException {
		_checkClosed();
		java.sql.Connection c = null;
		try {
			c = _pool.getConnection();
			// must do this, to reset connection state if the
			// connection is lost when an exception is thrown
			c.setAutoCommit(true);
		}
		catch(com.netx.generics.sql.ConnectionExhaustionException cee) {
			throw new ConnectionExhaustionException(cee);
		}
		catch(SQLException sqle) {
			if(getDriver().isDatabaseFailureException(sqle)) {
				throw new DatabaseFailureException(null, sqle, null);
			}
			else {
				throw new UnknownDatabaseException(null, sqle, null);
			}
		}
		Connection d = (Connection)_connectionCache.get(c.toString());
		if(d == null) {
			d = new Connection(this, c, _entities.keySet().iterator(), _listeners);
			_connectionCache.put(c.toString(), d);
		}
		else {
			d.reopen(c, _listeners);
		}
		// perform listener actions:
		Iterator<ConnectionListener> it = _listeners.iterator();
		while(it.hasNext()) {
			it.next().connectionRetrieved(d);
		}
		return d;
	}
	
	public ImmutableList<EntityMetaData> getEntityMetaData() {
		return new ImmutableList<EntityMetaData>(new ArrayList<EntityMetaData>(_entities.values()));
	}

	public ImmutableList<MetaData> getMetaData() {
		return new ImmutableList<MetaData>(new ArrayList<MetaData>(_metadata.values()));
	}

	public void addConnectionListener(ConnectionListener listener) {
		_checkClosed();
		if(listener == null) {
			throw new IllegalArgumentException("null ConnectionListener");
		}
		_listeners.add(listener);
	}

	public void createSchema() throws ErrorListException {
		//TODO impl
	}
	
	public void restartPool() throws DatabaseException {
		_checkClosed();
		ConnectionPool pool = _pool;
		_createConnectionPool();
		try {
			pool.close();
		}
		catch(SQLException sqle) {
			throw new UnknownDatabaseException(null, sqle, null);
		}
	}
	
	public int getNumActiveConnections() {
		_checkClosed();
		return _pool.getNumActiveConnections();
	}

	public int getNumIdleConnections() {
		_checkClosed();
		return _pool.getNumIdleConnections();
	}

	public void close() throws DatabaseException {
		try {
			if(!_closed) {
				_closed = true;
				_pool.close();
			}
		}
		catch(SQLException sqle) {
			throw new UnknownDatabaseException(null, sqle, null);
		}
	}

	// for Connection and Entity:
	EntityMetaData getMetaData(String name) {
		return (EntityMetaData)_entities.get(name.toLowerCase());
	}
	
	// for Connection:
	List<DataSink> getBackupSinks() {
		return _backup;
	}

	private void _checkClosed() {
		if(_closed) {
			throw new IllegalStateException("this datababase has already been closed");
		}
	}

	private void _createConnectionPool() {
		_pool = new ConnectionPool(_repository.driver.getJdbcDriverName(), _repository.url, _repository.username, _repository.password);
		_pool.setMaxActive(_repository.maxActive);
		_pool.setMaxIdle(_repository.maxIdle);
		_pool.setMaxWait(_repository.maxWait);
		_pool.setRemoveAbandonedTimeout(_repository.removeAbandonedTimeout);
	}
}
